package com.ouroboroswiki.core;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.ouroboroswiki.core.content.StaticContent;

public class ContentUtil {
	
	private static final char CHAR_CONTEXT_BREAK 	= '.';
	private static final String STR_CONTEXT_BREAK 	= ""+CHAR_CONTEXT_BREAK;
	private static final char CHAR_CONTEXT_CONTINUE = '-';
	private static final String STR_CONTEXT_CONTINUE= ""+CHAR_CONTEXT_CONTINUE;
	private static final char CHAR_CONTENT_GROUP_START = '(';
	private static final String STR_CONTEXT_GROUP_START= ""+CHAR_CONTENT_GROUP_START;
	
	private static final char CHAR_CONTENT_GROUP_END = ')';
	private static final char CHAR_CONTENT_GROUP_SPLIT = '|';
	private static final String CHAR_ALL = ""+
		CHAR_CONTEXT_BREAK+
		CHAR_CONTEXT_CONTINUE+
		CHAR_CONTENT_GROUP_START+
		CHAR_CONTENT_GROUP_END+
		CHAR_CONTENT_GROUP_SPLIT;
	
	private static final Logger log = Logger.getLogger(ContentUtil.class.getName());
	
	public static final String toString( List<ContentPath> paths ) {
		StringBuffer sb = new StringBuffer();
		toString(paths, sb);
		return sb.toString();
	}
	
	public static final void toString( List<ContentPath> paths, StringBuffer sb ) {
		switch( paths.size() ) {
		case 0:
			break;
		case 1:
			toString( paths.get(0), sb );
			break;
		default:
			sb.append(CHAR_CONTENT_GROUP_START);
			for( int i=0; i<paths.size(); i++ ) {
				toString( paths.get(i), sb );
				if( i < paths.size()-1 ) {
					sb.append(CHAR_CONTENT_GROUP_SPLIT);
				} else {
					sb.append(CHAR_CONTENT_GROUP_END);					
				}
			}
			break;
		}
	}
	
	public static final String toString( ContentPath path ) {
		StringBuffer s = new StringBuffer();
		toString( path, s );
		return s.toString();
	}
	
	public static final void toString( ContentPath path, StringBuffer s ) {
		if( path != null ) {
			s.insert(0, path.getName());
			List<ContentPath> children = path.getChildren();
			if( children == null || children.size() == 0 ) {
				// we're done
			} else {
				
				if( path.isContextualBreak() ) {
					s.insert(0, CHAR_CONTEXT_BREAK);
				} else {
					s.insert(0, CHAR_CONTEXT_CONTINUE);
				}
				if( children.size() == 1 ) {
					ContentPath child = children.get( 0 );
					toString( child, s );
				} else {
					s.insert(0, CHAR_CONTENT_GROUP_END);
					for( int i=children.size(); i>0; ) {
						i--;
						ContentPath child = children.get( i );
						toString( child, s );
						if( i > 0 ) {
							s.insert( 0, CHAR_CONTENT_GROUP_SPLIT );
						} else {
							s.insert( 0, CHAR_CONTENT_GROUP_START );
						}
					}
				}
			}			
		}
	}
	
	public static final ContentPath singleFromTokens( ArrayList<String> tokens ) {
		ContentPath result;
		String token = tokens.remove( tokens.size()-1 );
		if( tokens.size() > 0 ) {
			String breakerString = tokens.get( tokens.size()-1 );
			char breaker = breakerString.charAt(0);
			switch( breaker ) {
			case CHAR_CONTENT_GROUP_SPLIT:
			case CHAR_CONTENT_GROUP_START:
				result = new ContentPath(token);				
				break;
			case CHAR_CONTEXT_BREAK:
			case CHAR_CONTEXT_CONTINUE:
				tokens.remove( tokens.size()-1 );
				result = new ContentPath(
						token,
						listFromTokens(tokens), 
						breaker == CHAR_CONTEXT_BREAK
				);
				break;
			default:
				throw new IllegalArgumentException("unexpected input "+breakerString);
			}
			
		} else {
			result = new ContentPath(token);
		}
		return result;
	}
	
	public static final List<ContentPath> listFromTokens( ArrayList<String> tokens ) {
		
		List<ContentPath> result;
		if( tokens.size() > 0 ) {
			String token = tokens.get( tokens.size()-1 );
			char breaker = token.charAt(0);
			switch(breaker) {
			case CHAR_CONTENT_GROUP_END:
				// it's a list
				result = new ArrayList<ContentPath>();
				tokens.remove( tokens.size()-1 );
				while( breaker != CHAR_CONTENT_GROUP_START ) {
					ContentPath child = singleFromTokens( tokens );
					result.add( child );
					token = tokens.remove( tokens.size()-1 );
					breaker = token.charAt(0);
				}
				break;
			default:
				// it's not
				ContentPath child = singleFromTokens( tokens );
				result = Arrays.asList(child);
				break;
			}
		} else {
			result = Arrays.asList( new ContentPath("") );
		}
//		if( tokens.size() != 0 ) {
//			String token = tokens.remove( tokens.size()-1 );
//			char breaker;
//			if( tokens.size() > 0 ) {
//				String s = tokens.get( tokens.size()-1 );
//				breaker = s.charAt(0);
//				switch(breaker) {
//				case CHAR_CONTENT_GROUP_START:
//				case CHAR_CONTENT_GROUP_SPLIT:
//					result = new ContentPath( token );
//					break;
//				case CHAR_CONTEXT_BREAK:
//				case CHAR_CONTEXT_CONTINUE:
//					boolean contextBreak = breaker == CHAR_CONTEXT_BREAK;
//					tokens.remove( tokens.size()-1 );
//					if( tokens.size() > 0 ) {
//						String next = tokens.get( tokens.size()-1 );
//						char nextChar = next.charAt(0);
//						if( nextChar == CHAR_CONTENT_GROUP_END ) {
//							tokens.remove( tokens.size()-1 );
//							boolean done = false;
//							LinkedList<ContentPath> children = new LinkedList<ContentPath>();
//							while(!done) {
//								ContentPath child = fromTokens(tokens);
//								if( child != null ) {
//									children.add( 0, child );
//									String separator = tokens.remove(tokens.size() - 1);
//									char separatorChar = separator.charAt(0);
//									switch( separatorChar ) {
//									case CHAR_CONTENT_GROUP_START:
//										done = true;
//									case CHAR_CONTENT_GROUP_SPLIT:
//										break;
//									}
//								} else {
//									// TODO should never happen
//									done = true;
//								}
//							}
//							result = new ContentPath(
//									token, 
//									new ArrayList<ContentPath>(children), 
//									contextBreak
//							);
//						} else {
//							result = new ContentPath(token, fromTokens(tokens), contextBreak);
//						}
//						
//					} else {
//						result = new ContentPath(
//								token,
//								new ContentPath(""),
//								contextBreak
//						);
//					}
//					break;
//				default:
//					throw new IllegalArgumentException("unexpeced token "+s);
//				}
//			} else {
//				result = new ContentPath( token );
//			}
//		} else {
//			result = new ContentPath("");
//		}
		
		return result;
	}
	
	public static final ContentPath singleFromString( String s ) {
		List<ContentPath> list = listFromString(s);
		if( list.size() == 1 ) {
			return list.get(0);
		} else {
			throw new IllegalArgumentException( "input "+s+" resolves to "+list.size()+" paths" );
		}
	}
		
	public static final List<ContentPath> listFromString( String s ) {
		StringTokenizer st = new StringTokenizer(s, CHAR_ALL, true);
		ArrayList<String> tokens = new ArrayList<String>(st.countTokens());
		while(st.hasMoreTokens()) {
			tokens.add(st.nextToken());
		}
		List<ContentPath> result = listFromTokens(tokens);
		return result;
//		int index = s.indexOf(CHAR_CONTEXT_BREAK);
//		String filename;
//		String remainder;
//		if( index >= 0 ) {
//			filename = s.substring(0, index);
//			remainder = s.substring(index+1);
//		} else {
//			filename = s;
//			remainder = null;
//		}
//		ContentPath result = new ContentPath(filename, null, true );
//		boolean previousBreak = true;
//	    if( remainder != null ) {
//			StringTokenizer st = new StringTokenizer(remainder, CHAR_ALL, true);
//			try {
//				while( st.hasMoreElements() ) {
//					String token = st.nextToken();
//					String nextToken;
//					if( st.hasMoreTokens() ) {
//						nextToken = st.nextToken();
//					} else {
//						nextToken = null;
//					}
//					if( token.length() > 0 ) {
//						result = new ContentPath( token, result, previousBreak );
//					}
//					previousBreak = nextToken != null && nextToken.equals(STR_CONTEXT_BREAK);
//				}
//			} catch( Exception ex ) {
//				throw new RuntimeException( "unable to split "+s, ex );
//			}
//			
//		}
//		return result;
	}
	
	public static final Version getChildVersion( Version version, String childName ) {
		Version result;
		if( version == null ) {
			result = null;
		} else {
			if( version instanceof MultipartVersion ) {
				result = ((MultipartVersion)version).getVersions().get(childName);
			} else {
				result = null;
			}
		}
		return result;
	}
	
	public static final String flatten(String[] path ) {
		return flatten( path, 0, path.length );
	}
	
	public static final String toPipe( ContentPath path ) {
		StringBuffer sb = new StringBuffer();
		while( path.getChildren() != null && path.getChildren().size() == 1 ) {
			sb.insert(0, path.getName());
			path = path.getChildren().get( 0 );
			if( path.getChildren() != null && path.getChildren().size() == 1 ) {
				if( path.isContextualBreak() ) {
					sb.insert( 0, CHAR_CONTEXT_BREAK );
				} else {
					sb.insert( 0, CHAR_CONTEXT_CONTINUE );
				}
			}
		}
		return sb.toString();		
	}
	
	public static final String flatten(String[] path, int from, int to ) {
		StringBuffer sb = new StringBuffer();
		if( path.length > 0 ) {
			sb.append( path[path.length-1]);
			if( path.length > 1 ) {
				sb.append( '.' );
				for( int i=0; i<path.length-1; i++ ) {
					sb.append( path[i] );
					if( i < path.length - 2 ) {
						sb.append('.');
					}
				}
			}
		}
		return sb.toString();

	}
	
	public static final String[] append( String[] path, String toAppend ) {
		if( path == null ) {
			return new String[]{toAppend};
		} else {
			String[] result = new String[path.length + 1];
			System.arraycopy(path, 0, result, 0, path.length);
			result[ path.length ] = toAppend;
			return result;
		}
	}
	
	public static final String[] subpath( String[] path ) {
		String[] subpath = new String[path.length-1];
		System.arraycopy(path, 1, subpath, 0, path.length - 1);
		return subpath;
	}
	
	public static final String contentToString( Content content ) throws ContentException, IOException {
		try {
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			content.write(bos);
			String s = new String( bos.toByteArray() ); 
			return s;
		} catch( Exception ex ) {
			log.log(Level.WARNING, "unable to make string from content "+content, ex );
			return null;
		}
	}
	
	public static final InputStream openInputStream( Content content ) throws ContentException, IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		content.write(bos);
		return new ByteArrayInputStream( bos.toByteArray() );
	}
	
	public static final void pipe( InputStream ins, OutputStream outs ) throws IOException {
		int b;
		while( ( b = ins.read() ) >= 0 ) {
			outs.write( b );
		}
	}
	
	public static final Content pushToStaticContent( Content source ) throws ContentException, IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		source.write(bos);
		StaticContent staticContent = new StaticContent(bos.toByteArray());
		staticContent.setChildContent(source.getChildContent());
		assignAllAttributes( source, staticContent, source.getRepositoryName(), (Version)source.getVersion(), source.isWritable() );
		return staticContent;
	}
	
	public static final void assignAllAttributes( Content from, AbstractContent to, String repositoryName, Version version, boolean writable ) {
		to.setDynamic(from.isDynamic());
		to.setExists(from.exists());
		to.setMimeType(from.getMimeType());
		to.setReadable(from.isReadable());
		to.setRepositoryName(repositoryName);
		to.setUniqueName(from.getUniqueName());
		to.setUpdatedEditor(from.getUpdateEditor());
		to.setUpdateTime(from.getUpdateTime());
		to.setValidationResults(from.getValidationResults());
		to.setVersion(version);
		to.setWritable(writable);		
	}
	
	public static final ArrayList<ContentPath> split( ContentPath path ) {
		ArrayList<ContentPath> result = new ArrayList<ContentPath>();
		List<ContentPath> children = path.getChildren();
		if( children != null && children.size() > 0 ) {
			for( int i=0; i<children.size(); i++ ) {
				ContentPath child = children.get(i);
				ArrayList<ContentPath> childSplits = split( child );
				result.ensureCapacity( result.size() + childSplits.size() );
				for(int j=0; j<childSplits.size(); j++) {
					ContentPath childSplit = childSplits.get(j);
					ContentPath splitPath = new ContentPath(
							path.getName(), 
							childSplit, 
							path.isContextualBreak()
					);
					result.add(splitPath);
				}
			}			
		} else {
			result.add(path);
		}
		return result;
	}
	
	public static ContentPath mergeToPath(List<ContentPath> splits ) {
		merge(splits);
		if(splits.size() != 1 ) {
			throw new IllegalArgumentException("unexpected merge size "+splits.size());
		}
		return splits.get(0);
	}
	
	public static void merge( List<ContentPath> splits ) {
		if( splits != null ) {
			for( int i=splits.size(); i>0; ) {
				i--;
				ContentPath split = splits.get(i);
				for( int j=i; j>0; ) {
					j--;
					ContentPath compare = splits.get(j);
					if( split.getName().equals(compare.getName())) {
						List<ContentPath> compareChildren = compare.getChildren();
						if( compareChildren != null ) {
							split.getChildren().addAll( compareChildren );
						}
						splits.remove(j);
						i--;
					}
				}
				merge(split.getChildren());
			}			
		}
	}	
}
